library(tidyverse)
otu_abund <- read_csv("../../data/HMP2/abundance_HMP2.csv") %>%
column_to_rownames("...1")
meta_sample <- read_csv("../../data/HMP2/meta_samples_HMP2.csv") %>%
column_to_rownames("...1") %>% select(SubjectID) %>%
rownames_to_column("sample")
otu_taxon <- read_csv("../../data/HMP2/taxonomy_HMP2.csv") %>%
column_to_rownames("...1")
otu_taxon <- otu_taxon %>%
mutate(genus = str_replace_all(genus, "_", " "))
all(rownames(otu_abund)==meta_sample$sample) &
all(colnames(otu_abund)==rownames(otu_taxon))
[1] TRUE
# choose only sample with at least 25 sample to improve the clarity
# in the results
subject_preserved <- meta_sample$SubjectID %>% table %>%
subset(.>=25) %>% names
otu_abund_subset <- otu_abund[meta_sample$SubjectID %in% subject_preserved, ]
meta_sample_subset <- meta_sample[meta_sample$SubjectID %in% subject_preserved, ]
genus_rel_abund <- otu_abund_subset %>% rownames_to_column("sample") %>%
pivot_longer(-sample, names_to="otu", values_to="count") %>%
left_join(otu_taxon%>%select(genus:otu), by="otu") %>%
reframe(count=sum(count), .by=c("sample","genus")) %>%
pivot_wider(names_from=genus, values_from=count) %>%
column_to_rownames("sample")
genus_rel_abund <- genus_rel_abund / rowSums(genus_rel_abund)
# Useful function
prevalence <- function(X){colSums(X>0)/nrow(X)}
prevalence(genus_rel_abund) %>% enframe(value = "prevalence") %>%
ggplot(aes(x = prevalence)) +
geom_histogram(bins = 11, fill = "lightblue", color = "darkblue") +
scale_x_continuous(breaks = seq(0,1,.1))

# Remove the too rare genera
genus_rel_abund_filt <- genus_rel_abund[,prevalence(genus_rel_abund)>=.10]
subset_genera <- colSums(genus_rel_abund_filt) %>% sort %>% tail(10) %>% names
set.seed(42)
colorGenera <- rownames(qualpalr::qualpal(n = length(subset_genera),
colorspace = list(h = c(0,360),
s = c(.25,.6),
l = c(.45,.85)))$RGB) %>%
stats::setNames(subset_genera)
colorGenera["Bacteroides"] <- "#FF0000"
colorGenera["Prevotella"] <- "#00FF00"
colorGenera <- c(colorGenera, "Other"="#A6A6A6")
data_composition <- genus_rel_abund_filt %>%
rownames_to_column("sample") %>%
arrange(desc(Bacteroides - Prevotella)) %>%
mutate(sample_fct = fct_inorder(sample)) %>%
pivot_longer(-c(sample, sample_fct), names_to="genus", values_to="relative",
names_repair = "check_unique") %>%
mutate(genus = if_else(genus %in% subset_genera, genus, "Other")) %>%
reframe(relative=sum(relative), .by=c("sample", "sample_fct","genus")) %>%
left_join(meta_sample_subset, by = "sample")
p.composition <- data_composition %>%
ggplot(aes(x=sample_fct,y=relative,fill=genus)) +
geom_bar(stat="identity") +
scale_fill_manual(name = "Genus",
breaks=names(colorGenera),
values=colorGenera,
labels = function(x) paste0("<i>", x, "</i>")) +
theme_minimal() +
theme(legend.position="bottom",
axis.text.x=element_blank(),
axis.ticks.x=element_blank()) +
xlab("Samples") +
ylab("Genus\nComposition") +
guides(fill=guide_legend(ncol=5)) +
theme(legend.text = ggtext::element_markdown()) +
# Colored tile at bottom of each bar
ggnewscale::new_scale_fill() +
geom_tile(data = data_composition,
aes(x = sample, y = -0.03, fill = SubjectID),
height = 0.03, width = 1, inherit.aes = FALSE)
p.composition

Dimensionality Reduction
# Calculate the distance matrix with bray-curtis and aitchison
dist.bray <- vegan::vegdist(genus_rel_abund,"bray") %>% as.dist
dist.aitc <- genus_rel_abund_filt %>%
zCompositions::cmultRepl(z.warning = 1, z.delete = F) %>%
vegan::vegdist(method = "aitchison")
No. adjusted imputations: 12875
PCOA
pcoa.bray <- stats::cmdscale(dist.bray, eig=T, add=T)
pcoa.aitc <- stats::cmdscale(dist.aitc, eig=T, add=T)
Plot with Aitchison Distance
centroids.pcoa.aitc <- pcoa.aitc$points %>% as_tibble %>%
cbind("Subjects"=meta_sample_subset$SubjectID) %>%
reframe(V1=mean(V1),
V2=mean(V2),
.by=Subjects)
varExplained_aitc <- 100 * (pcoa.aitc$eig / sum(pcoa.aitc$eig)) %>%
round(2)
p.pcoa.aitc <- pcoa.aitc$points %>% as_tibble %>%
cbind("Subjects"=meta_sample_subset$SubjectID) %>%
ggplot(aes(x=V1,y=V2, color=Subjects)) +
geom_point(size=3) +
stat_ellipse() +
geom_point(data=centroids.pcoa.aitc, shape=21, size=5, color="black",
aes(fill=Subjects)) +
theme_minimal() +
xlab(paste("PCoA1 (~",varExplained_aitc[1],"%)", sep="")) +
ylab(paste("PCoA1 (~",varExplained_aitc[2],"%)", sep="")) +
theme(legend.position="bottom")
p.pcoa.aitc

varExplained_bray <- 100 * (pcoa.bray$eig / sum(pcoa.bray$eig)) %>%
round(2)
p.pcoa.bray <- pcoa.bray$points %>% as_tibble %>%
cbind("Bacteroides"=genus_rel_abund_filt[,"Bacteroides"]) %>%
cbind("Prevotella"=genus_rel_abund_filt[,"Prevotella"]) %>%
as_tibble() %>% mutate(sample=meta_sample_subset$sample) %>%
left_join(meta_sample_subset, by="sample") %>%
#mutate(enterotype=as.factor(enterotype)) %>%
#rename(Enterotype = enterotype) %>%
mutate(color_metric=Bacteroides-Prevotella) %>%
mutate(color_metric_2=rgb(Bacteroides,Prevotella,0)) %>%
ggplot(aes(x=V1, y=V2)) +
geom_point(aes(color=color_metric_2), size=3) +
scale_color_identity() +
theme_minimal() +
theme(legend.position="bottom",
legend.justification=1) +
xlab(paste("PCoA1 (~",varExplained_bray[1],"%)", sep="")) +
ylab(paste("PCoA1 (~",varExplained_bray[2],"%)", sep="")) +
guides(shape=guide_legend(label.position="top", title.position="top",
title.hjust=.5)) +
ggnewscale::new_scale_color() +
geom_point(aes(x = V1, y = V2, color = Prevotella), alpha = 0) +
scale_color_gradient(name="Prevotella", low="black", high="green",
limits=c(0,.75)) +
theme(legend.position="bottom") +
ggnewscale::new_scale_color() +
geom_point(aes(x = V1, y = V2, color = Bacteroides), alpha = 0) +
scale_color_gradient(name="Bacteroides", low="black", high="red",
limits=c(0,.75)) +
theme(legend.position = "bottom",
legend.justification=0)
p.pcoa.bray

Adonis 2
meta_sample_filt <- meta_sample %>%
filter(sample %in% rownames(genus_rel_abund_filt))
mat <- as.matrix(genus_rel_abund_filt[, c("Bacteroides", "Prevotella")])
vegan::adonis2(dist.aitc ~ SubjectID + mat,
data = meta_sample_filt,
permutations = 10^3, by = "margin")
Permutation test for adonis under reduced model
Marginal effects of terms
Permutation: free
Number of permutations: 1000
vegan::adonis2(formula = dist.aitc ~ SubjectID + mat, data = meta_sample_filt, permutations = 10^3, by = "margin")
Df SumOfSqs R2 F Pr(>F)
SubjectID 7 36793 0.36046 40.9461 0.000999 ***
mat 2 2464 0.02414 9.5985 0.000999 ***
Residual 406 52118 0.51059
Total 415 102073 1.00000
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
vegan::adonis2(dist.bray ~ SubjectID + mat, data = meta_sample_filt,
permutations = 10^3, by = "margin")
Permutation test for adonis under reduced model
Marginal effects of terms
Permutation: free
Number of permutations: 1000
vegan::adonis2(formula = dist.bray ~ SubjectID + mat, data = meta_sample_filt, permutations = 10^3, by = "margin")
Df SumOfSqs R2 F Pr(>F)
SubjectID 7 7.997 0.15215 25.87 0.000999 ***
mat 2 12.545 0.23865 142.03 0.000999 ***
Residual 406 17.930 0.34111
Total 415 52.564 1.00000
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
UMAP
library(uwot)
set.seed(42)
umap.bray <- uwot::umap(as.dist(dist.bray), min_dist = .5)
set.seed(42)
umap.aitc <- uwot::umap(as.dist(dist.aitc), min_dist = .5)
p.umap.aitc <- umap.aitc %>% as_tibble %>%
cbind("Subjects"=meta_sample_subset$SubjectID) %>%
ggplot(aes(x=V1,y=V2, fill=Subjects)) +
geom_point(size=3, alpha =.75, shape = 21) +
theme_minimal() +
xlab("UMAP-1") + ylab("UMAP-2") +
theme(legend.position="bottom")
p.umap.bray <- umap.bray %>% as_tibble %>%
cbind("Bacteroides"=genus_rel_abund_filt[,"Bacteroides"]) %>%
cbind("Prevotella"=genus_rel_abund_filt[,"Prevotella"]) %>%
as_tibble() %>% mutate(sample=meta_sample_subset$sample) %>%
left_join(meta_sample_subset, by="sample") %>%
mutate(color_metric=Bacteroides-Prevotella) %>%
mutate(color_metric_2=rgb(Bacteroides,Prevotella,0)) %>%
ggplot(aes(x=V1, y=V2)) +
geom_point(aes(color=color_metric_2), size=3) +
scale_color_identity() +
theme_minimal() +
theme(legend.position="bottom",
legend.justification=1) +
xlab("UMAP-1") + ylab("UMAP-2") +
guides(shape=guide_legend(label.position="top", title.position="top",
title.hjust=.5)) +
ggnewscale::new_scale_color() +
geom_point(aes(x = V1, y = V2, color = Prevotella), alpha = 0) +
scale_color_gradient(name="Prevotella", low="black", high="green",
limits=c(0,.75)) +
theme(legend.position="bottom") +
ggnewscale::new_scale_color() +
geom_point(aes(x = V1, y = V2, color = Bacteroides), alpha = 0) +
scale_color_gradient(name="Bacteroides", low="black", high="red",
limits=c(0,.75)) +
theme(legend.position = "bottom",
legend.justification=0)
p.umap <- ggpubr::ggarrange(
p.umap.bray + theme(plot.margin = margin(b = 15, t = 5)) + ggtitle("G-HMP2 Bray-Curtis"),
p.umap.aitc + ggtitle("G-HMP2 Aitchison"),
ncol = 2
)
p.umap

png(filename = "../Supplementary/HMP2_umap_metrics.png", width = 3000, height = 1800, res = 300)
p.umap
dev.off()
null device
1
Other Metrics
pcoa.euc <- dist(genus_rel_abund_filt) %>%
stats::cmdscale(eig=T, add=T)
pcoa.JSD <- philentropy::distance(genus_rel_abund_filt, method = "jensen-shannon") %>%
stats::cmdscale(eig=T, add=T)
pcoa.CAN <- vegan::vegdist(genus_rel_abund_filt, method = "canberra") %>%
stats::cmdscale(eig=T, add=T)
pcoa.HEL <- vegan::vegdist(genus_rel_abund_filt, method = "hellinger") %>%
stats::cmdscale(eig=T, add=T)
plot.PCOA.ent <- function(pcoa, meta_sample_filt, genus_rel_filt){
varExplained_bray <- round(100 * (pcoa$eig / sum(pcoa$eig)))
p <- pcoa$points %>% as_tibble %>%
cbind("Bacteroides"=genus_rel_abund_filt[,"Bacteroides"]) %>%
cbind("Prevotella"=genus_rel_abund_filt[,"Prevotella"]) %>%
as_tibble() %>% mutate(sample=meta_sample_subset$sample) %>%
left_join(meta_sample_subset, by="sample") %>%
mutate(color_metric=Bacteroides-Prevotella) %>%
mutate(color_metric_2=rgb(Bacteroides,Prevotella,0)) %>%
ggplot(aes(x=V1, y=V2)) +
geom_point(aes(color=color_metric_2), size=3) +
scale_color_identity() +
theme_minimal() +
theme(legend.position="bottom",
legend.justification=1) +
xlab(paste("PCoA1 (~",varExplained_bray[1],"%)", sep="")) +
ylab(paste("PCoA1 (~",varExplained_bray[2],"%)", sep="")) +
guides(shape=guide_legend(label.position="top", title.position="top",
title.hjust=.5)) +
ggnewscale::new_scale_color() +
geom_point(aes(x = V1, y = V2, color = Prevotella), alpha = 0) +
scale_color_gradient(name="Prevotella", low="black", high="green",
limits=c(0,.75)) +
theme(legend.position="bottom") +
ggnewscale::new_scale_color() +
geom_point(aes(x = V1, y = V2, color = Bacteroides), alpha = 0) +
scale_color_gradient(name="Bacteroides", low="black", high="red",
limits=c(0,.75)) +
theme(legend.position = "bottom",
legend.justification=0)
return(p)
}
plot.PCOA.sub <- function(pcoa, meta_sample_filt, genus_rel_filt){
varExplained_aitc <- 100 * (pcoa$eig / sum(pcoa$eig)) %>%
round(2)
p <- pcoa$points %>% as_tibble %>%
cbind("Subjects"=meta_sample_filt$SubjectID) %>%
ggplot(aes(x=V1,y=V2, color=Subjects)) +
geom_point(size=3) +
theme_minimal() +
xlab(paste("PCoA1 (~",varExplained_aitc[1],"%)", sep="")) +
ylab(paste("PCoA1 (~",varExplained_aitc[2],"%)", sep="")) +
theme(legend.position="none")
return(p)
}
p.euc.ent <- plot.PCOA.ent(pcoa.euc, meta_sample_filt, genus_rel_abund_filt)
p.JSD.ent <- plot.PCOA.ent(pcoa.JSD, meta_sample_filt, genus_rel_abund_filt)
p.CAN.ent <- plot.PCOA.ent(pcoa.CAN, meta_sample_filt, genus_rel_abund_filt)
p.HEL.ent <- plot.PCOA.ent(pcoa.HEL, meta_sample_filt, genus_rel_abund_filt)
p.AIT.ent <- plot.PCOA.ent(pcoa.aitc, meta_sample_filt, genus_rel_abund_filt)
p.BRA.ent <- plot.PCOA.ent(pcoa.bray, meta_sample_filt, genus_rel_abund_filt)
p.euc.sub <- plot.PCOA.sub(pcoa.euc, meta_sample_filt, genus_rel_abund_filt)
p.JSD.sub <- plot.PCOA.sub(pcoa.JSD, meta_sample_filt, genus_rel_abund_filt)
p.CAN.sub <- plot.PCOA.sub(pcoa.CAN, meta_sample_filt, genus_rel_abund_filt)
p.HEL.sub <- plot.PCOA.sub(pcoa.HEL, meta_sample_filt, genus_rel_abund_filt)
p.AIT.sub <- plot.PCOA.sub(pcoa.aitc, meta_sample_filt, genus_rel_abund_filt)
p.BRA.sub <- plot.PCOA.sub(pcoa.bray, meta_sample_filt, genus_rel_abund_filt)
spacer_title <- ggplot() +
theme_void() +
ggtitle("G-HMP2 Bacteroides/Prevotella") +
theme(plot.title = element_text(hjust = 0.5, size = 20, face = "bold"))
p.ent <- ggpubr::ggarrange(
p.euc.ent + ggtitle("Euclidian") + theme(title = element_text(size = 16)),
p.JSD.ent + ggtitle("Jensen-Shannon") + theme(title = element_text(size = 16)),
p.CAN.ent + ggtitle("Canberra") + theme(title = element_text(size = 16)),
p.HEL.ent + ggtitle("Hellinger") + theme(title = element_text(size = 16)),
p.BRA.ent + ggtitle("Bray-Curtis") + theme(title = element_text(size = 16)),
p.AIT.ent + ggtitle("Aitchison") + theme(title = element_text(size = 16)),
ncol = 6, legend = "none"
)
p.ent <- ggpubr::ggarrange(spacer_title, p.ent, nrow = 2, heights = c(.1, .9))
p.ent

spacer_title <- ggplot() +
theme_void() +
ggtitle("G-HMP2 Subjects") +
theme(plot.title = element_text(hjust = 0.5, size = 20, face = "bold"))
p.sub <- ggpubr::ggarrange(
p.euc.sub + ggtitle("Euclidian") + theme(title = element_text(size = 16)),
p.JSD.sub + ggtitle("Jensen-Shannon") + theme(title = element_text(size = 16)),
p.CAN.sub + ggtitle("Canberra") + theme(title = element_text(size = 16)),
p.HEL.sub + ggtitle("Hellinger") + theme(title = element_text(size = 16)),
p.BRA.sub + ggtitle("Bray-Curtis") + theme(title = element_text(size = 16)),
p.AIT.sub + ggtitle("Aitchison") + theme(title = element_text(size = 16)),
ncol = 6, common.legend = T, legend = "none"
)
p.sub <- ggpubr::ggarrange(spacer_title, p.sub, nrow = 2, heights = c(.1, .9))
p.sub

pall <- ggpubr::ggarrange(p.ent, p.sub, nrow = 2) + theme(legend.position = "none")
pall

png("../Supplementary/HMP2_pcoa_more_metrics.png", width = 2*7200, height = 2*2700, res = 2*300)
pall
dev.off()
null device
1
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpgYGAKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQpvdHVfYWJ1bmQgPC0gcmVhZF9jc3YoIi4uLy4uL2RhdGEvSE1QMi9hYnVuZGFuY2VfSE1QMi5jc3YiKSAlPiUKICBjb2x1bW5fdG9fcm93bmFtZXMoIi4uLjEiKQptZXRhX3NhbXBsZSA8LSByZWFkX2NzdigiLi4vLi4vZGF0YS9ITVAyL21ldGFfc2FtcGxlc19ITVAyLmNzdiIpICU+JQogIGNvbHVtbl90b19yb3duYW1lcygiLi4uMSIpICU+JSBzZWxlY3QoU3ViamVjdElEKSAlPiUKICByb3duYW1lc190b19jb2x1bW4oInNhbXBsZSIpCm90dV90YXhvbiA8LSByZWFkX2NzdigiLi4vLi4vZGF0YS9ITVAyL3RheG9ub215X0hNUDIuY3N2IikgJT4lCiAgY29sdW1uX3RvX3Jvd25hbWVzKCIuLi4xIikKYGBgCgpgYGB7cn0Kb3R1X3RheG9uIDwtIG90dV90YXhvbiAlPiUKICBtdXRhdGUoZ2VudXMgPSBzdHJfcmVwbGFjZV9hbGwoZ2VudXMsICJfIiwgIiAiKSkKYGBgCgpgYGB7cn0KYWxsKHJvd25hbWVzKG90dV9hYnVuZCk9PW1ldGFfc2FtcGxlJHNhbXBsZSkgJgphbGwoY29sbmFtZXMob3R1X2FidW5kKT09cm93bmFtZXMob3R1X3RheG9uKSkKYGBgCgpgYGB7cn0KIyBjaG9vc2Ugb25seSBzYW1wbGUgd2l0aCBhdCBsZWFzdCAyNSBzYW1wbGUgdG8gaW1wcm92ZSB0aGUgY2xhcml0eSAKIyBpbiB0aGUgcmVzdWx0cwpzdWJqZWN0X3ByZXNlcnZlZCA8LSBtZXRhX3NhbXBsZSRTdWJqZWN0SUQgJT4lIHRhYmxlICU+JQogIHN1YnNldCguPj0yNSkgJT4lIG5hbWVzCgpvdHVfYWJ1bmRfc3Vic2V0IDwtIG90dV9hYnVuZFttZXRhX3NhbXBsZSRTdWJqZWN0SUQgJWluJSBzdWJqZWN0X3ByZXNlcnZlZCwgXQptZXRhX3NhbXBsZV9zdWJzZXQgPC0gbWV0YV9zYW1wbGVbbWV0YV9zYW1wbGUkU3ViamVjdElEICVpbiUgc3ViamVjdF9wcmVzZXJ2ZWQsIF0KYGBgCgpgYGB7cn0KZ2VudXNfcmVsX2FidW5kIDwtIG90dV9hYnVuZF9zdWJzZXQgJT4lIHJvd25hbWVzX3RvX2NvbHVtbigic2FtcGxlIikgJT4lCiAgcGl2b3RfbG9uZ2VyKC1zYW1wbGUsIG5hbWVzX3RvPSJvdHUiLCB2YWx1ZXNfdG89ImNvdW50IikgJT4lCiAgbGVmdF9qb2luKG90dV90YXhvbiU+JXNlbGVjdChnZW51czpvdHUpLCBieT0ib3R1IikgJT4lCiAgcmVmcmFtZShjb3VudD1zdW0oY291bnQpLCAuYnk9Yygic2FtcGxlIiwiZ2VudXMiKSkgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbT1nZW51cywgdmFsdWVzX2Zyb209Y291bnQpICU+JQogIGNvbHVtbl90b19yb3duYW1lcygic2FtcGxlIikKZ2VudXNfcmVsX2FidW5kIDwtIGdlbnVzX3JlbF9hYnVuZCAvIHJvd1N1bXMoZ2VudXNfcmVsX2FidW5kKQpgYGAKCmBgYHtyfQojIFVzZWZ1bCBmdW5jdGlvbgpwcmV2YWxlbmNlIDwtIGZ1bmN0aW9uKFgpe2NvbFN1bXMoWD4wKS9ucm93KFgpfQpgYGAKCmBgYHtyfQpwcmV2YWxlbmNlKGdlbnVzX3JlbF9hYnVuZCkgJT4lIGVuZnJhbWUodmFsdWUgPSAicHJldmFsZW5jZSIpICU+JQogIGdncGxvdChhZXMoeCA9IHByZXZhbGVuY2UpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDExLCBmaWxsID0gImxpZ2h0Ymx1ZSIsIGNvbG9yID0gImRhcmtibHVlIikgKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwxLC4xKSkKYGBgCgoKYGBge3J9CiMgUmVtb3ZlIHRoZSB0b28gcmFyZSBnZW5lcmEKZ2VudXNfcmVsX2FidW5kX2ZpbHQgPC0gZ2VudXNfcmVsX2FidW5kWyxwcmV2YWxlbmNlKGdlbnVzX3JlbF9hYnVuZCk+PS4xMF0KYGBgCgpgYGB7cn0Kc3Vic2V0X2dlbmVyYSA8LSBjb2xTdW1zKGdlbnVzX3JlbF9hYnVuZF9maWx0KSAlPiUgc29ydCAlPiUgdGFpbCgxMCkgJT4lIG5hbWVzCmBgYAoKYGBge3J9CnNldC5zZWVkKDQyKQpjb2xvckdlbmVyYSA8LSByb3duYW1lcyhxdWFscGFscjo6cXVhbHBhbChuID0gbGVuZ3RoKHN1YnNldF9nZW5lcmEpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3JzcGFjZSA9IGxpc3QoaCA9IGMoMCwzNjApLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzID0gYyguMjUsLjYpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsID0gYyguNDUsLjg1KSkpJFJHQikgJT4lCiAgc3RhdHM6OnNldE5hbWVzKHN1YnNldF9nZW5lcmEpCmNvbG9yR2VuZXJhWyJCYWN0ZXJvaWRlcyJdIDwtICIjRkYwMDAwIgpjb2xvckdlbmVyYVsiUHJldm90ZWxsYSJdIDwtICIjMDBGRjAwIgpjb2xvckdlbmVyYSA8LSBjKGNvbG9yR2VuZXJhLCAiT3RoZXIiPSIjQTZBNkE2IikKYGBgCgpgYGB7cn0KZGF0YV9jb21wb3NpdGlvbiA8LSBnZW51c19yZWxfYWJ1bmRfZmlsdCAlPiUKICByb3duYW1lc190b19jb2x1bW4oInNhbXBsZSIpICU+JQogIGFycmFuZ2UoZGVzYyhCYWN0ZXJvaWRlcyAtIFByZXZvdGVsbGEpKSAlPiUKICBtdXRhdGUoc2FtcGxlX2ZjdCA9IGZjdF9pbm9yZGVyKHNhbXBsZSkpICU+JQogIHBpdm90X2xvbmdlcigtYyhzYW1wbGUsIHNhbXBsZV9mY3QpLCBuYW1lc190bz0iZ2VudXMiLCB2YWx1ZXNfdG89InJlbGF0aXZlIiwgCiAgICAgICAgICAgICAgIG5hbWVzX3JlcGFpciA9ICJjaGVja191bmlxdWUiKSAlPiUKICBtdXRhdGUoZ2VudXMgPSBpZl9lbHNlKGdlbnVzICVpbiUgc3Vic2V0X2dlbmVyYSwgZ2VudXMsICJPdGhlciIpKSAlPiUKICByZWZyYW1lKHJlbGF0aXZlPXN1bShyZWxhdGl2ZSksIC5ieT1jKCJzYW1wbGUiLCAic2FtcGxlX2ZjdCIsImdlbnVzIikpICU+JQogIGxlZnRfam9pbihtZXRhX3NhbXBsZV9zdWJzZXQsIGJ5ID0gInNhbXBsZSIpCgpwLmNvbXBvc2l0aW9uIDwtIGRhdGFfY29tcG9zaXRpb24gJT4lCiAgZ2dwbG90KGFlcyh4PXNhbXBsZV9mY3QseT1yZWxhdGl2ZSxmaWxsPWdlbnVzKSkgKwogICAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArCiAgICBzY2FsZV9maWxsX21hbnVhbChuYW1lID0gIkdlbnVzIiwKICAgICAgICAgICAgICAgICAgICAgIGJyZWFrcz1uYW1lcyhjb2xvckdlbmVyYSksCiAgICAgICAgICAgICAgICAgICAgICB2YWx1ZXM9Y29sb3JHZW5lcmEsCiAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBmdW5jdGlvbih4KSBwYXN0ZTAoIjxpPiIsIHgsICI8L2k+IikpICsKICAgIHRoZW1lX21pbmltYWwoKSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIsCiAgICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRpY2tzLng9ZWxlbWVudF9ibGFuaygpKSArCiAgeGxhYigiU2FtcGxlcyIpICsKICB5bGFiKCJHZW51c1xuQ29tcG9zaXRpb24iKSArCiAgZ3VpZGVzKGZpbGw9Z3VpZGVfbGVnZW5kKG5jb2w9NSkpICsKICB0aGVtZShsZWdlbmQudGV4dCA9IGdndGV4dDo6ZWxlbWVudF9tYXJrZG93bigpKSArCiAgIyBDb2xvcmVkIHRpbGUgYXQgYm90dG9tIG9mIGVhY2ggYmFyCiAgZ2duZXdzY2FsZTo6bmV3X3NjYWxlX2ZpbGwoKSArCiAgZ2VvbV90aWxlKGRhdGEgPSBkYXRhX2NvbXBvc2l0aW9uLAogICAgICAgICAgICBhZXMoeCA9IHNhbXBsZSwgeSA9IC0wLjAzLCBmaWxsID0gU3ViamVjdElEKSwgCiAgICAgICAgICAgIGhlaWdodCA9IDAuMDMsIHdpZHRoID0gMSwgaW5oZXJpdC5hZXMgPSBGQUxTRSkgCnAuY29tcG9zaXRpb24KYGBgCgoKIyMgRGltZW5zaW9uYWxpdHkgUmVkdWN0aW9uCgpgYGB7cn0KIyBDYWxjdWxhdGUgdGhlIGRpc3RhbmNlIG1hdHJpeCB3aXRoIGJyYXktY3VydGlzIGFuZCBhaXRjaGlzb24KZGlzdC5icmF5IDwtIHZlZ2FuOjp2ZWdkaXN0KGdlbnVzX3JlbF9hYnVuZCwiYnJheSIpICU+JSBhcy5kaXN0CmRpc3QuYWl0YyA8LSBnZW51c19yZWxfYWJ1bmRfZmlsdCAlPiUgCiAgekNvbXBvc2l0aW9uczo6Y211bHRSZXBsKHoud2FybmluZyA9IDEsIHouZGVsZXRlID0gRikgJT4lCiAgdmVnYW46OnZlZ2Rpc3QobWV0aG9kID0gImFpdGNoaXNvbiIpCmBgYAoKIyMgUENPQQoKYGBge3J9CnBjb2EuYnJheSA8LSBzdGF0czo6Y21kc2NhbGUoZGlzdC5icmF5LCBlaWc9VCwgYWRkPVQpCnBjb2EuYWl0YyA8LSBzdGF0czo6Y21kc2NhbGUoZGlzdC5haXRjLCBlaWc9VCwgYWRkPVQpCmBgYAoKIyMgUGxvdCB3aXRoIEFpdGNoaXNvbiBEaXN0YW5jZQoKYGBge3J9CmNlbnRyb2lkcy5wY29hLmFpdGMgPC0gcGNvYS5haXRjJHBvaW50cyAlPiUgYXNfdGliYmxlICU+JQogIGNiaW5kKCJTdWJqZWN0cyI9bWV0YV9zYW1wbGVfc3Vic2V0JFN1YmplY3RJRCkgJT4lCiAgcmVmcmFtZShWMT1tZWFuKFYxKSwgCiAgICAgICAgICBWMj1tZWFuKFYyKSwKICAgICAgICAgIC5ieT1TdWJqZWN0cykKCnZhckV4cGxhaW5lZF9haXRjIDwtIDEwMCAqIChwY29hLmFpdGMkZWlnIC8gc3VtKHBjb2EuYWl0YyRlaWcpKSAlPiUKICByb3VuZCgyKQoKcC5wY29hLmFpdGMgPC0gIHBjb2EuYWl0YyRwb2ludHMgJT4lIGFzX3RpYmJsZSAlPiUgCiAgY2JpbmQoIlN1YmplY3RzIj1tZXRhX3NhbXBsZV9zdWJzZXQkU3ViamVjdElEKSAlPiUKICBnZ3Bsb3QoYWVzKHg9VjEseT1WMiwgY29sb3I9U3ViamVjdHMpKSArCiAgZ2VvbV9wb2ludChzaXplPTMpICsKICBzdGF0X2VsbGlwc2UoKSArCiAgZ2VvbV9wb2ludChkYXRhPWNlbnRyb2lkcy5wY29hLmFpdGMsIHNoYXBlPTIxLCBzaXplPTUsIGNvbG9yPSJibGFjayIsCiAgICAgICAgICAgICBhZXMoZmlsbD1TdWJqZWN0cykpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHhsYWIocGFzdGUoIlBDb0ExICh+Iix2YXJFeHBsYWluZWRfYWl0Y1sxXSwiJSkiLCBzZXA9IiIpKSArCiAgeWxhYihwYXN0ZSgiUENvQTEgKH4iLHZhckV4cGxhaW5lZF9haXRjWzJdLCIlKSIsIHNlcD0iIikpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIpCgpwLnBjb2EuYWl0YwpgYGAKCmBgYHtyfQp2YXJFeHBsYWluZWRfYnJheSA8LSAxMDAgKiAocGNvYS5icmF5JGVpZyAvIHN1bShwY29hLmJyYXkkZWlnKSkgJT4lCiAgcm91bmQoMikKCnAucGNvYS5icmF5IDwtIHBjb2EuYnJheSRwb2ludHMgJT4lIGFzX3RpYmJsZSAlPiUgCiAgY2JpbmQoIkJhY3Rlcm9pZGVzIj1nZW51c19yZWxfYWJ1bmRfZmlsdFssIkJhY3Rlcm9pZGVzIl0pICU+JQogIGNiaW5kKCJQcmV2b3RlbGxhIj1nZW51c19yZWxfYWJ1bmRfZmlsdFssIlByZXZvdGVsbGEiXSkgJT4lCiAgYXNfdGliYmxlKCkgJT4lIG11dGF0ZShzYW1wbGU9bWV0YV9zYW1wbGVfc3Vic2V0JHNhbXBsZSkgJT4lCiAgbGVmdF9qb2luKG1ldGFfc2FtcGxlX3N1YnNldCwgYnk9InNhbXBsZSIpICU+JQogICNtdXRhdGUoZW50ZXJvdHlwZT1hcy5mYWN0b3IoZW50ZXJvdHlwZSkpICU+JQogICNyZW5hbWUoRW50ZXJvdHlwZSA9IGVudGVyb3R5cGUpICU+JQogIG11dGF0ZShjb2xvcl9tZXRyaWM9QmFjdGVyb2lkZXMtUHJldm90ZWxsYSkgJT4lCiAgbXV0YXRlKGNvbG9yX21ldHJpY18yPXJnYihCYWN0ZXJvaWRlcyxQcmV2b3RlbGxhLDApKSAlPiUKICBnZ3Bsb3QoYWVzKHg9VjEsIHk9VjIpKSArCiAgICBnZW9tX3BvaW50KGFlcyhjb2xvcj1jb2xvcl9tZXRyaWNfMiksIHNpemU9MykgKwogICAgc2NhbGVfY29sb3JfaWRlbnRpdHkoKSArCiAgICB0aGVtZV9taW5pbWFsKCkgKwogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iLAogICAgICAgICAgbGVnZW5kLmp1c3RpZmljYXRpb249MSkgKwogICAgeGxhYihwYXN0ZSgiUENvQTEgKH4iLHZhckV4cGxhaW5lZF9icmF5WzFdLCIlKSIsIHNlcD0iIikpICsKICAgIHlsYWIocGFzdGUoIlBDb0ExICh+Iix2YXJFeHBsYWluZWRfYnJheVsyXSwiJSkiLCBzZXA9IiIpKSArCiAgICBndWlkZXMoc2hhcGU9Z3VpZGVfbGVnZW5kKGxhYmVsLnBvc2l0aW9uPSJ0b3AiLCB0aXRsZS5wb3NpdGlvbj0idG9wIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGl0bGUuaGp1c3Q9LjUpKSArCiAgICBnZ25ld3NjYWxlOjpuZXdfc2NhbGVfY29sb3IoKSArCiAgICAgIGdlb21fcG9pbnQoYWVzKHggPSBWMSwgeSA9IFYyLCBjb2xvciA9IFByZXZvdGVsbGEpLCBhbHBoYSA9IDApICsKICAgICAgc2NhbGVfY29sb3JfZ3JhZGllbnQobmFtZT0iUHJldm90ZWxsYSIsIGxvdz0iYmxhY2siLCBoaWdoPSJncmVlbiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBsaW1pdHM9YygwLC43NSkpICsKICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iKSArCiAgICBnZ25ld3NjYWxlOjpuZXdfc2NhbGVfY29sb3IoKSArCiAgICAgIGdlb21fcG9pbnQoYWVzKHggPSBWMSwgeSA9IFYyLCBjb2xvciA9IEJhY3Rlcm9pZGVzKSwgYWxwaGEgPSAwKSArCiAgICAgIHNjYWxlX2NvbG9yX2dyYWRpZW50KG5hbWU9IkJhY3Rlcm9pZGVzIiwgbG93PSJibGFjayIsIGhpZ2g9InJlZCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBsaW1pdHM9YygwLC43NSkpICsKICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICAgICAgICAgIGxlZ2VuZC5qdXN0aWZpY2F0aW9uPTApCgpwLnBjb2EuYnJheQpgYGAKCgojIyBNZXJnZSB0aGUgZmlndXJlcwoKYGBge3IsIGZpZy53aWR0aD0xMy4zLCBmaWcuaGVpZ2h0PTEwfQpwMSA8LSBwLmNvbXBvc2l0aW9uIAoKcDIgPC0gZ2dwdWJyOjpnZ2FycmFuZ2UocC5wY29hLmJyYXkgKyB0aGVtZShsZWdlbmQubWFyZ2luID0gbWFyZ2luKHQgPSAyMCwgYiA9IDEwKSksCiAgICAgICAgICAgICAgICAgICAgICAgIHAucGNvYS5haXRjLCBuY29sPTIsIGxhYmVscz1jKCJCIiwiQyIpKQoKcGFsbCA8LSBnZ3B1YnI6OmdnYXJyYW5nZShwMSwgcDIsIAogICAgICAgICAgICAgICAgICAgICAgICAgIG5jb2w9MSwgbGFiZWxzPWMoIkEiLCIiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBoZWlnaHRzPWMoMC40LDAuNikpCnBhbGwKYGBgCgoKYGBge3J9CnBuZyhmaWxlbmFtZT0iSE1QMl9wY29hX21ldHJpY3MucG5nIiwgd2lkdGg9ODAwMCwgaGVpZ2h0PTYwMDAsIHJlcz02MDApCnBhbGwKZGV2Lm9mZigpCmBgYAoKIyMgQWRvbmlzIDIKCmBgYHtyfQptZXRhX3NhbXBsZV9maWx0IDwtIG1ldGFfc2FtcGxlICU+JQogIGZpbHRlcihzYW1wbGUgJWluJSByb3duYW1lcyhnZW51c19yZWxfYWJ1bmRfZmlsdCkpIApgYGAKCmBgYHtyfQptYXQgPC0gYXMubWF0cml4KGdlbnVzX3JlbF9hYnVuZF9maWx0WywgYygiQmFjdGVyb2lkZXMiLCAiUHJldm90ZWxsYSIpXSkKdmVnYW46OmFkb25pczIoZGlzdC5haXRjIH4gU3ViamVjdElEICsgbWF0LAogICAgICAgICAgICAgICBkYXRhID0gbWV0YV9zYW1wbGVfZmlsdCwgCiAgICAgICAgICAgICAgIHBlcm11dGF0aW9ucyA9IDEwXjMsIGJ5ID0gIm1hcmdpbiIpCmBgYAoKCmBgYHtyfQp2ZWdhbjo6YWRvbmlzMihkaXN0LmJyYXkgfiBTdWJqZWN0SUQgKyBtYXQsIGRhdGEgPSBtZXRhX3NhbXBsZV9maWx0LCAKICAgICAgICAgICAgICAgcGVybXV0YXRpb25zID0gMTBeMywgYnkgPSAibWFyZ2luIikKYGBgCgojIyBVTUFQCgpgYGB7cixtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KHV3b3QpCnNldC5zZWVkKDQyKQp1bWFwLmJyYXkgPC0gdXdvdDo6dW1hcChhcy5kaXN0KGRpc3QuYnJheSksIG1pbl9kaXN0ID0gLjUpCgpzZXQuc2VlZCg0MikKdW1hcC5haXRjIDwtIHV3b3Q6OnVtYXAoYXMuZGlzdChkaXN0LmFpdGMpLCBtaW5fZGlzdCA9IC41KQpgYGAKCmBgYHtyfQpwLnVtYXAuYWl0YyA8LSB1bWFwLmFpdGMgJT4lIGFzX3RpYmJsZSAlPiUgCiAgY2JpbmQoIlN1YmplY3RzIj1tZXRhX3NhbXBsZV9zdWJzZXQkU3ViamVjdElEKSAlPiUKICBnZ3Bsb3QoYWVzKHg9VjEseT1WMiwgZmlsbD1TdWJqZWN0cykpICsKICBnZW9tX3BvaW50KHNpemU9MywgYWxwaGEgPS43NSwgc2hhcGUgPSAyMSkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgeGxhYigiVU1BUC0xIikgKyB5bGFiKCJVTUFQLTIiKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iKQpgYGAKCmBgYHtyfQpwLnVtYXAuYnJheSA8LSB1bWFwLmJyYXkgJT4lIGFzX3RpYmJsZSAlPiUgCiAgICBjYmluZCgiQmFjdGVyb2lkZXMiPWdlbnVzX3JlbF9hYnVuZF9maWx0WywiQmFjdGVyb2lkZXMiXSkgJT4lCiAgICBjYmluZCgiUHJldm90ZWxsYSI9Z2VudXNfcmVsX2FidW5kX2ZpbHRbLCJQcmV2b3RlbGxhIl0pICU+JQogICAgYXNfdGliYmxlKCkgJT4lIG11dGF0ZShzYW1wbGU9bWV0YV9zYW1wbGVfc3Vic2V0JHNhbXBsZSkgJT4lCiAgICBsZWZ0X2pvaW4obWV0YV9zYW1wbGVfc3Vic2V0LCBieT0ic2FtcGxlIikgJT4lCiAgICBtdXRhdGUoY29sb3JfbWV0cmljPUJhY3Rlcm9pZGVzLVByZXZvdGVsbGEpICU+JQogICAgbXV0YXRlKGNvbG9yX21ldHJpY18yPXJnYihCYWN0ZXJvaWRlcyxQcmV2b3RlbGxhLDApKSAlPiUKICAgIGdncGxvdChhZXMoeD1WMSwgeT1WMikpICsKICAgICAgZ2VvbV9wb2ludChhZXMoY29sb3I9Y29sb3JfbWV0cmljXzIpLCBzaXplPTMpICsKICAgICAgc2NhbGVfY29sb3JfaWRlbnRpdHkoKSArCiAgICAgIHRoZW1lX21pbmltYWwoKSArCiAgICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0iYm90dG9tIiwKICAgICAgICAgICAgbGVnZW5kLmp1c3RpZmljYXRpb249MSkgKwogICAgICB4bGFiKCJVTUFQLTEiKSArIHlsYWIoIlVNQVAtMiIpICsKICAgICAgZ3VpZGVzKHNoYXBlPWd1aWRlX2xlZ2VuZChsYWJlbC5wb3NpdGlvbj0idG9wIiwgdGl0bGUucG9zaXRpb249InRvcCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGl0bGUuaGp1c3Q9LjUpKSArCiAgICAgIGdnbmV3c2NhbGU6Om5ld19zY2FsZV9jb2xvcigpICsKICAgICAgICBnZW9tX3BvaW50KGFlcyh4ID0gVjEsIHkgPSBWMiwgY29sb3IgPSBQcmV2b3RlbGxhKSwgYWxwaGEgPSAwKSArCiAgICAgICAgc2NhbGVfY29sb3JfZ3JhZGllbnQobmFtZT0iUHJldm90ZWxsYSIsIGxvdz0iYmxhY2siLCBoaWdoPSJncmVlbiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpbWl0cz1jKDAsLjc1KSkgKwogICAgICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0iYm90dG9tIikgKwogICAgICBnZ25ld3NjYWxlOjpuZXdfc2NhbGVfY29sb3IoKSArCiAgICAgICAgZ2VvbV9wb2ludChhZXMoeCA9IFYxLCB5ID0gVjIsIGNvbG9yID0gQmFjdGVyb2lkZXMpLCBhbHBoYSA9IDApICsKICAgICAgICBzY2FsZV9jb2xvcl9ncmFkaWVudChuYW1lPSJCYWN0ZXJvaWRlcyIsIGxvdz0iYmxhY2siLCBoaWdoPSJyZWQiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaW1pdHM9YygwLC43NSkpICsKICAgICAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgICAgICAgICAgICBsZWdlbmQuanVzdGlmaWNhdGlvbj0wKQpgYGAKCmBgYHtyLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9Nn0KcC51bWFwIDwtIGdncHVicjo6Z2dhcnJhbmdlKAogIHAudW1hcC5icmF5ICsgdGhlbWUocGxvdC5tYXJnaW4gPSBtYXJnaW4oYiA9IDE1LCB0ID0gNSkpICsgZ2d0aXRsZSgiRy1ITVAyIEJyYXktQ3VydGlzIiksIAogIHAudW1hcC5haXRjICsgZ2d0aXRsZSgiRy1ITVAyIEFpdGNoaXNvbiIpLCAKICBuY29sID0gMgopCnAudW1hcApgYGAKCmBgYHtyfQpwbmcoZmlsZW5hbWUgPSAiLi4vU3VwcGxlbWVudGFyeS9ITVAyX3VtYXBfbWV0cmljcy5wbmciLCB3aWR0aCA9IDMwMDAsIGhlaWdodCA9IDE4MDAsIHJlcyA9IDMwMCkKcC51bWFwCmRldi5vZmYoKQpgYGAKCgojIyBPdGhlciBNZXRyaWNzCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KcGNvYS5ldWMgPC0gZGlzdChnZW51c19yZWxfYWJ1bmRfZmlsdCkgJT4lCiAgc3RhdHM6OmNtZHNjYWxlKGVpZz1ULCBhZGQ9VCkKcGNvYS5KU0QgPC0gcGhpbGVudHJvcHk6OmRpc3RhbmNlKGdlbnVzX3JlbF9hYnVuZF9maWx0LCBtZXRob2QgPSAiamVuc2VuLXNoYW5ub24iKSAlPiUKICBzdGF0czo6Y21kc2NhbGUoZWlnPVQsIGFkZD1UKQpwY29hLkNBTiA8LSB2ZWdhbjo6dmVnZGlzdChnZW51c19yZWxfYWJ1bmRfZmlsdCwgbWV0aG9kID0gImNhbmJlcnJhIikgJT4lCiAgc3RhdHM6OmNtZHNjYWxlKGVpZz1ULCBhZGQ9VCkKcGNvYS5IRUwgPC0gdmVnYW46OnZlZ2Rpc3QoZ2VudXNfcmVsX2FidW5kX2ZpbHQsIG1ldGhvZCA9ICJoZWxsaW5nZXIiKSAlPiUKICBzdGF0czo6Y21kc2NhbGUoZWlnPVQsIGFkZD1UKQpgYGAKCmBgYHtyfQpwbG90LlBDT0EuZW50IDwtIGZ1bmN0aW9uKHBjb2EsIG1ldGFfc2FtcGxlX2ZpbHQsIGdlbnVzX3JlbF9maWx0KXsKICAKICB2YXJFeHBsYWluZWRfYnJheSA8LSByb3VuZCgxMDAgKiAocGNvYSRlaWcgLyBzdW0ocGNvYSRlaWcpKSkKCiAgcCA8LSBwY29hJHBvaW50cyAlPiUgYXNfdGliYmxlICU+JSAKICAgIGNiaW5kKCJCYWN0ZXJvaWRlcyI9Z2VudXNfcmVsX2FidW5kX2ZpbHRbLCJCYWN0ZXJvaWRlcyJdKSAlPiUKICAgIGNiaW5kKCJQcmV2b3RlbGxhIj1nZW51c19yZWxfYWJ1bmRfZmlsdFssIlByZXZvdGVsbGEiXSkgJT4lCiAgICBhc190aWJibGUoKSAlPiUgbXV0YXRlKHNhbXBsZT1tZXRhX3NhbXBsZV9zdWJzZXQkc2FtcGxlKSAlPiUKICAgIGxlZnRfam9pbihtZXRhX3NhbXBsZV9zdWJzZXQsIGJ5PSJzYW1wbGUiKSAlPiUKICAgIG11dGF0ZShjb2xvcl9tZXRyaWM9QmFjdGVyb2lkZXMtUHJldm90ZWxsYSkgJT4lCiAgICBtdXRhdGUoY29sb3JfbWV0cmljXzI9cmdiKEJhY3Rlcm9pZGVzLFByZXZvdGVsbGEsMCkpICU+JQogICAgZ2dwbG90KGFlcyh4PVYxLCB5PVYyKSkgKwogICAgICBnZW9tX3BvaW50KGFlcyhjb2xvcj1jb2xvcl9tZXRyaWNfMiksIHNpemU9MykgKwogICAgICBzY2FsZV9jb2xvcl9pZGVudGl0eSgpICsKICAgICAgdGhlbWVfbWluaW1hbCgpICsKICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iLAogICAgICAgICAgICBsZWdlbmQuanVzdGlmaWNhdGlvbj0xKSArCiAgICAgIHhsYWIocGFzdGUoIlBDb0ExICh+Iix2YXJFeHBsYWluZWRfYnJheVsxXSwiJSkiLCBzZXA9IiIpKSArCiAgICAgIHlsYWIocGFzdGUoIlBDb0ExICh+Iix2YXJFeHBsYWluZWRfYnJheVsyXSwiJSkiLCBzZXA9IiIpKSArCiAgICAgIGd1aWRlcyhzaGFwZT1ndWlkZV9sZWdlbmQobGFiZWwucG9zaXRpb249InRvcCIsIHRpdGxlLnBvc2l0aW9uPSJ0b3AiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpdGxlLmhqdXN0PS41KSkgKwogICAgICBnZ25ld3NjYWxlOjpuZXdfc2NhbGVfY29sb3IoKSArCiAgICAgICAgZ2VvbV9wb2ludChhZXMoeCA9IFYxLCB5ID0gVjIsIGNvbG9yID0gUHJldm90ZWxsYSksIGFscGhhID0gMCkgKwogICAgICAgIHNjYWxlX2NvbG9yX2dyYWRpZW50KG5hbWU9IlByZXZvdGVsbGEiLCBsb3c9ImJsYWNrIiwgaGlnaD0iZ3JlZW4iLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaW1pdHM9YygwLC43NSkpICsKICAgICAgICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIpICsKICAgICAgZ2duZXdzY2FsZTo6bmV3X3NjYWxlX2NvbG9yKCkgKwogICAgICAgIGdlb21fcG9pbnQoYWVzKHggPSBWMSwgeSA9IFYyLCBjb2xvciA9IEJhY3Rlcm9pZGVzKSwgYWxwaGEgPSAwKSArCiAgICAgICAgc2NhbGVfY29sb3JfZ3JhZGllbnQobmFtZT0iQmFjdGVyb2lkZXMiLCBsb3c9ImJsYWNrIiwgaGlnaD0icmVkIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGltaXRzPWMoMCwuNzUpKSArCiAgICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICAgICAgICAgICAgbGVnZW5kLmp1c3RpZmljYXRpb249MCkKCiAgCiAgcmV0dXJuKHApCn0KYGBgCgpgYGB7cn0KcGxvdC5QQ09BLnN1YiA8LSBmdW5jdGlvbihwY29hLCBtZXRhX3NhbXBsZV9maWx0LCBnZW51c19yZWxfZmlsdCl7CiAgCiAgdmFyRXhwbGFpbmVkX2FpdGMgPC0gMTAwICogKHBjb2EkZWlnIC8gc3VtKHBjb2EkZWlnKSkgJT4lCiAgcm91bmQoMikKCiAgcCA8LSBwY29hJHBvaW50cyAlPiUgYXNfdGliYmxlICU+JSAKICAgIGNiaW5kKCJTdWJqZWN0cyI9bWV0YV9zYW1wbGVfZmlsdCRTdWJqZWN0SUQpICU+JQogICAgZ2dwbG90KGFlcyh4PVYxLHk9VjIsIGNvbG9yPVN1YmplY3RzKSkgKwogICAgZ2VvbV9wb2ludChzaXplPTMpICsKICAgIHRoZW1lX21pbmltYWwoKSArCiAgICB4bGFiKHBhc3RlKCJQQ29BMSAofiIsdmFyRXhwbGFpbmVkX2FpdGNbMV0sIiUpIiwgc2VwPSIiKSkgKwogICAgeWxhYihwYXN0ZSgiUENvQTEgKH4iLHZhckV4cGxhaW5lZF9haXRjWzJdLCIlKSIsIHNlcD0iIikpICsKICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpCiAgCiAgcmV0dXJuKHApCn0KYGBgCgoKYGBge3J9CnAuZXVjLmVudCA8LSBwbG90LlBDT0EuZW50KHBjb2EuZXVjLCBtZXRhX3NhbXBsZV9maWx0LCBnZW51c19yZWxfYWJ1bmRfZmlsdCkKcC5KU0QuZW50IDwtIHBsb3QuUENPQS5lbnQocGNvYS5KU0QsIG1ldGFfc2FtcGxlX2ZpbHQsIGdlbnVzX3JlbF9hYnVuZF9maWx0KQpwLkNBTi5lbnQgPC0gcGxvdC5QQ09BLmVudChwY29hLkNBTiwgbWV0YV9zYW1wbGVfZmlsdCwgZ2VudXNfcmVsX2FidW5kX2ZpbHQpCnAuSEVMLmVudCA8LSBwbG90LlBDT0EuZW50KHBjb2EuSEVMLCBtZXRhX3NhbXBsZV9maWx0LCBnZW51c19yZWxfYWJ1bmRfZmlsdCkKcC5BSVQuZW50IDwtIHBsb3QuUENPQS5lbnQocGNvYS5haXRjLCBtZXRhX3NhbXBsZV9maWx0LCBnZW51c19yZWxfYWJ1bmRfZmlsdCkKcC5CUkEuZW50IDwtIHBsb3QuUENPQS5lbnQocGNvYS5icmF5LCBtZXRhX3NhbXBsZV9maWx0LCBnZW51c19yZWxfYWJ1bmRfZmlsdCkKYGBgCgpgYGB7cn0KcC5ldWMuc3ViIDwtIHBsb3QuUENPQS5zdWIocGNvYS5ldWMsIG1ldGFfc2FtcGxlX2ZpbHQsIGdlbnVzX3JlbF9hYnVuZF9maWx0KQpwLkpTRC5zdWIgPC0gcGxvdC5QQ09BLnN1YihwY29hLkpTRCwgbWV0YV9zYW1wbGVfZmlsdCwgZ2VudXNfcmVsX2FidW5kX2ZpbHQpCnAuQ0FOLnN1YiA8LSBwbG90LlBDT0Euc3ViKHBjb2EuQ0FOLCBtZXRhX3NhbXBsZV9maWx0LCBnZW51c19yZWxfYWJ1bmRfZmlsdCkKcC5IRUwuc3ViIDwtIHBsb3QuUENPQS5zdWIocGNvYS5IRUwsIG1ldGFfc2FtcGxlX2ZpbHQsIGdlbnVzX3JlbF9hYnVuZF9maWx0KQpwLkFJVC5zdWIgPC0gcGxvdC5QQ09BLnN1YihwY29hLmFpdGMsIG1ldGFfc2FtcGxlX2ZpbHQsIGdlbnVzX3JlbF9hYnVuZF9maWx0KQpwLkJSQS5zdWIgPC0gcGxvdC5QQ09BLnN1YihwY29hLmJyYXksIG1ldGFfc2FtcGxlX2ZpbHQsIGdlbnVzX3JlbF9hYnVuZF9maWx0KQpgYGAKCmBgYHtyfQpzcGFjZXJfdGl0bGUgPC0gZ2dwbG90KCkgKyAKICB0aGVtZV92b2lkKCkgKwogIGdndGl0bGUoIkctSE1QMiBCYWN0ZXJvaWRlcy9QcmV2b3RlbGxhIikgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIHNpemUgPSAyMCwgZmFjZSA9ICJib2xkIikpCmBgYAoKYGBge3IsIGZpZy53aWR0aD0yNCwgZmlnLmhlaWdodD00LjV9CnAuZW50IDwtIGdncHVicjo6Z2dhcnJhbmdlKAogIHAuZXVjLmVudCArIGdndGl0bGUoIkV1Y2xpZGlhbiIpICsgdGhlbWUodGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE2KSksCiAgcC5KU0QuZW50ICsgZ2d0aXRsZSgiSmVuc2VuLVNoYW5ub24iKSArIHRoZW1lKHRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNikpLCAKICBwLkNBTi5lbnQgKyBnZ3RpdGxlKCJDYW5iZXJyYSIpICsgdGhlbWUodGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE2KSksIAogIHAuSEVMLmVudCArIGdndGl0bGUoIkhlbGxpbmdlciIpICsgdGhlbWUodGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE2KSksCiAgcC5CUkEuZW50ICsgZ2d0aXRsZSgiQnJheS1DdXJ0aXMiKSArIHRoZW1lKHRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNikpLAogIHAuQUlULmVudCArIGdndGl0bGUoIkFpdGNoaXNvbiIpICsgdGhlbWUodGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE2KSksIAogIG5jb2wgPSA2LCBsZWdlbmQgPSAibm9uZSIKKQpwLmVudCA8LSBnZ3B1YnI6OmdnYXJyYW5nZShzcGFjZXJfdGl0bGUsIHAuZW50LCBucm93ID0gMiwgaGVpZ2h0cyA9IGMoLjEsIC45KSkKcC5lbnQKYGBgCgpgYGB7cn0Kc3BhY2VyX3RpdGxlIDwtIGdncGxvdCgpICsgCiAgdGhlbWVfdm9pZCgpICsKICBnZ3RpdGxlKCJHLUhNUDIgU3ViamVjdHMiKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgc2l6ZSA9IDIwLCBmYWNlID0gImJvbGQiKSkKYGBgCgpgYGB7ciwgZmlnLndpZHRoPTI0LCBmaWcuaGVpZ2h0PTQuNX0KcC5zdWIgPC0gZ2dwdWJyOjpnZ2FycmFuZ2UoCiAgcC5ldWMuc3ViICsgZ2d0aXRsZSgiRXVjbGlkaWFuIikgKyB0aGVtZSh0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTYpKSwgCiAgcC5KU0Quc3ViICsgZ2d0aXRsZSgiSmVuc2VuLVNoYW5ub24iKSArIHRoZW1lKHRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNikpLCAKICBwLkNBTi5zdWIgKyBnZ3RpdGxlKCJDYW5iZXJyYSIpICsgdGhlbWUodGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE2KSksIAogIHAuSEVMLnN1YiArIGdndGl0bGUoIkhlbGxpbmdlciIpICsgdGhlbWUodGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE2KSksCiAgcC5CUkEuc3ViICsgZ2d0aXRsZSgiQnJheS1DdXJ0aXMiKSArIHRoZW1lKHRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNikpLAogIHAuQUlULnN1YiArIGdndGl0bGUoIkFpdGNoaXNvbiIpICsgdGhlbWUodGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE2KSksIAogIG5jb2wgPSA2LCBjb21tb24ubGVnZW5kID0gVCwgbGVnZW5kID0gIm5vbmUiCikKcC5zdWIgPC0gZ2dwdWJyOjpnZ2FycmFuZ2Uoc3BhY2VyX3RpdGxlLCBwLnN1YiwgbnJvdyA9IDIsIGhlaWdodHMgPSBjKC4xLCAuOSkpCnAuc3ViCmBgYAoKYGBge3IsIGZpZy53aWR0aD0yNCwgZmlnLmhlaWdodD05fQpwYWxsIDwtIGdncHVicjo6Z2dhcnJhbmdlKHAuZW50LCBwLnN1YiwgbnJvdyA9IDIpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQpwYWxsCmBgYAoKYGBge3J9CnBuZygiLi4vU3VwcGxlbWVudGFyeS9ITVAyX3Bjb2FfbW9yZV9tZXRyaWNzLnBuZyIsIHdpZHRoID0gMio3MjAwLCBoZWlnaHQgPSAyKjI3MDAsIHJlcyA9IDIqMzAwKQpwYWxsCmRldi5vZmYoKQpgYGAKCgoKCgoK